using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CRM.Models;
using CRM.Models.Email;
using CRM.Services.SystemManage;
using CRM.ViewModels.Email;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RushMail;
using RushMail.Requests;

namespace CRM.Services.Email;

public class RushMailApplicationService : BaseService
{
    private IRushMailClient _rushMailClient;
    private readonly IOemEmailApplicationFactory _applicationFactory;
    private readonly ILogger<RushMailApplicationService> _logger;
    private readonly MyDbContext _dbContext;
    private readonly CompanyService _companyService;
    private readonly CompanyPointsConfigService _companyPointsConfigService;
    private readonly EmailMarketingService _emailMarketingService;

    public RushMailApplicationService(IOemEmailApplicationFactory applicationFactory,
        ILogger<RushMailApplicationService> logger, MyDbContext dbContext, CompanyService companyService,
        CompanyPointsConfigService companyPointsConfigService, EmailMarketingService emailMarketingService)
    {
        _applicationFactory = applicationFactory;
        _logger = logger;
        _dbContext = dbContext;
        _companyService = companyService;
        _companyPointsConfigService = companyPointsConfigService;
        _emailMarketingService = emailMarketingService;
    }

    public async Task RunAsync(EmailMarketing emailMarketing)
    {
        _dbContext.Entry(emailMarketing).State = EntityState.Unchanged;
        await _dbContext.Entry(emailMarketing).Reference(e => e.EmailSenderName).LoadAsync();

        var companyConfig = _companyService.GetInfo(emailMarketing.CompanyId);

        var json = emailMarketing.AddresseeObj;
        if (json == null)
        {
            _logger.LogInformation("没有收件人信息,跳过发送邮件");
            return;
        }

        if (companyConfig == null)
        {
            _logger.LogError("无法找到公司id为{CompanyId}的数据,跳过发送邮件", emailMarketing.CompanyId);
            return;
        }

        const int maxSize = 1024 * 1024 * 10;
        if (emailMarketing.EmailContent.Length > maxSize)
        {
            var user = await DbContext.Users.FindAsync(emailMarketing.UserId);
            _emailMarketingService.CurrentUser = user;
            await _emailMarketingService.SetState(emailMarketing.Id, EmailMarketing.StateEnum.Canceled,
                EmailMarkeingLog.TypeEnum.Negative, "邮件内容大于10MB");
            _emailMarketingService.CurrentUser = null;
            _logger.LogError("#{TaskId}邮件内容大于10MB, 已取消", emailMarketing.Id);
            await _dbContext.SaveChangesAsync();
            return;
        }

        var numberOfPoints = companyConfig.NumberOfPoints;
        // TODO: 为每个公司拿单独的配置项,目前拿的是通用的
        // 配置项快照
        var companyPointsConfig = _companyPointsConfigService.GetInfo(emailMarketing.CompanyId) ??
                                  _companyPointsConfigService.GetInfo(1);
        var expectsTheRestOfTheIntegral = numberOfPoints - json.Count * companyPointsConfig.SendEmailPoints;
        //  TODO: 判断预计积分是否够使用 【 --未加锁,后期需要对积分进行加锁(预防同一时间建立的大量任务) 】
        if (numberOfPoints >= 0 && expectsTheRestOfTheIntegral >= 0)
        {
            var link =
                (await _dbContext.RushMailLinks.FirstOrDefaultAsync(r => r.EmailMarketingId == emailMarketing.Id))
                ?? _dbContext.RushMailLinks.Add(new RushMailLink() { EmailMarketingId = emailMarketing.Id }).Entity;
            if (link.State == RushMailState.Completed)
            {
                emailMarketing.SetState(EmailMarketing.StateEnum.Completed);
                await _dbContext.SaveChangesAsync();
                return;
            }
            
            _rushMailClient = await _applicationFactory.CreateRushMailClient(emailMarketing.OemId);
            // TODO: 清除一定时间以上的无效数据，地址池，模板
            // 创建地址池
            var poolId = await CreateAddressPool(emailMarketing, link, emailMarketing.AddresseeObj);
            _logger.LogInformation("#{TaskId}创建地址池执行完成, id: {PoolId}", emailMarketing.Id, poolId);
            // 创建模板
            // TODO: 如有重复模板关联rushmail模板id
            var templateId = 0;
            try
            {
                templateId = await CreateEmailTemplate(emailMarketing, link);
            }
            catch (Exception e)
            {
                _logger.LogError(e, "创建模板抛出异常");
            }

            _logger.LogInformation("#{TaskId}创建模板执行完成, id: {TemplateId}", emailMarketing.Id, templateId);

            if (emailMarketing.EmailSenderName is { RushmailEmailId: null })
            {
                emailMarketing.EmailSenderName.RushmailEmailId = await CreateSenderName(emailMarketing.EmailSenderName);
            }

            if (poolId != 0 && templateId != 0 && link.TaskId == 0)
            {
                // 发送邮件
                var sendFlag = await SendEmail(emailMarketing, link, poolId, templateId);
                if (sendFlag)
                {
                    emailMarketing.State = EmailMarketing.StateEnum.Submitted;
                    link.State = RushMailState.Send;
                    _dbContext.AddRange(emailMarketing.AddresseeObj
                        .Select(m => EmailSendHistoryVM.NewRushmailModel(emailMarketing, m)));

                    var SendEmailPoints = companyPointsConfig.SendEmailPoints;
                    // 计算有效发出数量并扣除积分
                    if (SendEmailPoints != 0)
                    {
                        // 扣积分
                        var effectiveSendPoints = emailMarketing.AddresseeObj.Count * SendEmailPoints;
                        companyConfig.NumberOfPoints -= effectiveSendPoints;
                        // 记录积分日志
                        var consumptionType = PointsConsumptionLog.ConsumptionTypeEnum.SendingEmailByServer;
                        var UserInfo = DbContext.Users.First(m => m.Id == emailMarketing.UserId);

                        var ext = new JObject()
                        {
                            {
                                "Addressee", JArray.FromObject(emailMarketing.AddresseeObj)
                            },
                            {
                                "Id", emailMarketing.Id
                            },
                            {
                                "EmailMarketingName", emailMarketing.Title
                            },
                            {
                                "TimingTransmission", emailMarketing.TimingTransmission
                            }
                        };

                        var pointsConsumptionLogData = new PointsConsumptionLog()
                        {
                            UserId = emailMarketing.UserId,
                            GroupId = emailMarketing.GroupId,
                            CompanyId = emailMarketing.CompanyId,
                            OemId = emailMarketing.OemId,
                            ConsumptionType = consumptionType,
                            Ext = ext.ToString(),
                            OperateUser = UserInfo.TrueName,
                            PointsNum = effectiveSendPoints
                        };
                        DbContext.PointsConsumptionLog.Add(pointsConsumptionLogData);
                    }
                }
            }
        }
        else
        {
            var user = await DbContext.Users.FindAsync(emailMarketing.UserId);
            _emailMarketingService.CurrentUser = user;
            await _emailMarketingService.SetState(emailMarketing.Id, EmailMarketing.StateEnum.Canceled,
                EmailMarkeingLog.TypeEnum.Negative, "积分不足，营销任务被自动取消。");
            _emailMarketingService.CurrentUser = null;
        }


        await _dbContext.SaveChangesAsync();
    }

    /// <summary>
    /// 跟踪发送数量
    /// </summary>
    /// <param name="link"></param>
    public async Task TrackSendNumAsync(RushMailLink link)
    {
        if (link is not { TaskId: > 0 })
            return;

        _dbContext.Entry(link).State = EntityState.Unchanged;
        var emailMarketing =
            await _dbContext.EmailMarketing.Where(m => m.Id == link.EmailMarketingId).FirstOrDefaultAsync();
        if (emailMarketing is null)
        {
            _logger.LogError("无法找到rushmail所关联的营销id#{EmailMarketingId}", link.EmailMarketingId);
            return;
        }

        link.LatestTrackTime = DateTime.Now;
        _rushMailClient = await _applicationFactory.CreateRushMailClient(emailMarketing.OemId);
        var response = await _rushMailClient.Task.View(link.TaskId);
        if (response.IsOk && response.Result != null)
        {
            emailMarketing.SendingNum = response.Result.Task.CountSend + response.Result.Task.CountError;

            if (response.Result.Task.CountError > 0)
            {
                // TODO 获取详细失败列表
            }

            // 所有邮件发送完成
            if (emailMarketing.SendingNum >= emailMarketing.AddresseeObj.Count)
                link.State = RushMailState.NeedTrack;

            if (response.Result.Task.CountSend == 0 && !string.IsNullOrEmpty(response.Result.Task.RejectReason))
            {
                var user = await DbContext.Users.FindAsync(emailMarketing.UserId);
                // await _emailMarketingService.SetState(emailMarketing.Id, EmailMarketing.StateEnum.Canceled, EmailMarkeingLog.TypeEnum.Negative, $"外部消息: {response.Result.Task.RejectReason}");
                // 改状态
                emailMarketing.SetState(EmailMarketing.StateEnum.Canceled);
                // 加日志
                _dbContext.EmailMarkeingLog.Add(new EmailMarkeingLogVM().NewModel(user, emailMarketing.Id,
                    EmailMarkeingLog.TypeEnum.Negative, $"外部消息: {response.Result.Task.RejectReason}"));
                link.State = RushMailState.Completed;
            }
        }

        await _dbContext.SaveChangesAsync();
    }

    /// <summary>
    /// 跟踪已读数量
    /// </summary>
    /// <param name="link"></param>
    public async Task TrackReadNum(RushMailLink link)
    {
        if (link is not { TaskId: > 0 })
            return;

        _dbContext.Entry(link).State = EntityState.Unchanged;
        var emailMarketing =
            await _dbContext.EmailMarketing.Where(m => m.Id == link.EmailMarketingId).FirstOrDefaultAsync();
        if (emailMarketing is null)
        {
            _logger.LogError("无法找到rushmail所关联的营销id#{EmailMarketingId}", link.EmailMarketingId);
            return;
        }

        _rushMailClient = await _applicationFactory.CreateRushMailClient(emailMarketing.OemId);
        var histories = await _dbContext.EmailSendHistory.Where(m => m.Source == emailMarketing.Id)
            .Select(m => new EmailSendHistory()
            {
                Id = m.Id,
                Details = m.Details,
                ReceiverEmail = m.ReceiverEmail
            }).ToDictionaryAsync(x => x.ReceiverEmail, x => x);

        if (link.TrackId <= 0)
        {
            var response = await _rushMailClient.Track.Overview(link.TaskId);
            if (response.IsOk)
                link.TrackId = response.Result.TrackId;
        }

        if (link.TrackId > 0)
        {
            var trackId = link.TrackId;
            var detailResponse = await _rushMailClient.Track.Detail(trackId);
            if (detailResponse.IsOk)
            {
                if (detailResponse.Result is { Count: > 0 } tracks)
                {
                    var addresseeList = emailMarketing.AddresseeObj;
                    var tracksDic = tracks.ToDictionary(x => x.Address, x => x);
                    foreach (var addressee in addresseeList.Where(m => !m.IsRead))
                    {
                        if (tracksDic.TryGetValue(addressee.Addressee, out var item))
                        {
                            addressee.IsRead = item.OpenCount > 0;
                        }

                        if (histories.TryGetValue(addressee.Addressee, out var history))
                        {
                            history.Details |= (long)DetailsEnum.Open;
                        }
                        else
                        {
                            history = EmailSendHistoryVM.NewRushmailModel(emailMarketing, addressee);
                            history.Details |= (long)DetailsEnum.Open;
                            _dbContext.Add(history);
                        }
                    }

                    emailMarketing.Addressee = JsonConvert.SerializeObject(addresseeList);
                    emailMarketing.OpenNum = addresseeList.Count(m => m.IsRead);
                    // 当所有发送成功的邮件已读时停止跟踪
                    if (addresseeList.All(m => m.IsRead || m.IsFailed))
                    {
                        link.State = RushMailState.Completed;
                        emailMarketing.SetState(EmailMarketing.StateEnum.Completed);
                    }
                }
            }
        }

        link.LatestTrackTime = DateTime.Now;
        await _dbContext.SaveChangesAsync();
    }

    private async Task<bool> SendEmail(EmailMarketing emailMarketing, RushMailLink link, int poolId, int templateId)
    {
        if (link.TaskId != 0)
            return true;

        var taskResponse = await _rushMailClient.Task.Add(new TaskAdd()
        {
            PoolId = poolId,
            TemplateId = templateId,
            EmailId = emailMarketing?.EmailSenderName?.RushmailEmailId,
            TaskName = emailMarketing.Title,
            ReplyTo = emailMarketing.ReplyEmail,
            CountPreset = 0,
            Status = -9,
            Track = 2,
        });

        if (!taskResponse.IsOk)
        {
            LogRequestError("邮件发送任务创建", emailMarketing.Id, taskResponse.Content.ToString(Formatting.None));
            return false;
        }

        var taskId = taskResponse.Result;
        link.TaskId = taskId;
        _logger.LogInformation("营销任务#{Id}发送任务创建成功", link.EmailMarketingId);
        // 后续用于跟踪统计
        return true;
    }

    /// <summary>
    /// 创建地址池
    /// </summary>
    /// <param name="emailMarketing"></param>
    /// <param name="link"></param>
    /// <param name="emailMarketingAddresseeObj"></param>
    /// <returns></returns>
    private async Task<int> CreateAddressPool(EmailMarketing emailMarketing, RushMailLink link,
        List<AddresseeObj> emailMarketingAddresseeObj)
    {
        if (link.PoolId != 0)
            return link.PoolId;

        var now = DateTime.Now;
        var nowStr = now.ToString("yyyyMMddHHmmss");
        var poolResponse = await _rushMailClient.Pool.Add(new PoolAdd()
        {
            Name = emailMarketing.Title + nowStr,
            PublicName = emailMarketing.Title + nowStr,
        });

        if (!poolResponse.IsOk)
        {
            LogRequestError("地址池创建", emailMarketing.Id, poolResponse.Content.ToString(Formatting.None));
            return 0;
        }

        var poolId = poolResponse.Result;
        link.PoolId = poolId;
        var poolAddressResponse = await _rushMailClient.PoolAddress.Add(new PoolAddressAdd()
        {
            PoolId = poolId,
            Data = emailMarketingAddresseeObj.Select(e => new PoolAddressItem()
            {
                Address = e.Addressee,
                Name = e.Name,
            }).ToList()
        });

        if (!poolAddressResponse.IsOk)
        {
            LogRequestError("地址池添加邮箱", emailMarketing.Id, poolAddressResponse.Content.ToString(Formatting.None));
            return 0;
        }

        _logger.LogInformation("营销任务#{Id}地址池创建成功", link.EmailMarketingId);
        return poolId;
    }

    /// <summary>
    /// 创建邮件模板
    /// </summary>
    /// <param name="emailMarketing"></param>
    /// <param name="link"></param>
    /// <returns></returns>
    private async Task<int> CreateEmailTemplate(EmailMarketing emailMarketing, RushMailLink link)
    {
        if (link.TemplateId != 0)
            return link.TemplateId;

        var now = DateTime.Now;
        var nowStr = now.ToString("yyyyMMddHHmmss");
        var templateResponse = await _rushMailClient.Template.Add(new TemplateAdd()
        {
            Name = emailMarketing.Title + nowStr,
            Mode = "editor",
            Subject = emailMarketing.EmailSubject,
            TplContent = emailMarketing.EmailContent
        });
        if (!templateResponse.IsOk)
        {
            LogRequestError("模板创建", emailMarketing.Id, templateResponse.Content.ToString(Formatting.None));
            return 0;
        }

        var templateId = templateResponse.Result;
        link.TemplateId = templateId;
        _logger.LogInformation("营销任务#{Id}邮件模板创建成功", link.EmailMarketingId);
        return templateId;
    }

    /// <summary>
    /// 创建发件人名称
    /// </summary>
    /// <param name="emailSenderName"></param>
    /// <returns></returns>
    private async Task<int?> CreateSenderName(EmailSenderName emailSenderName)
    {
        var flag = false;
        var retry = 3;
        int? result = null;
        RushMailResponse<int>? latestResponse = null;
        while (!flag && retry > 0)
        {
            latestResponse = await _rushMailClient.DomainEmail.Add(new()
            {
                Name = emailSenderName.Name,
                Account = RandName(),
                DomainId = 79,
                Password = "Ab12345678",
                Status = 1,
            });

            flag = latestResponse.IsOk;
            result = latestResponse.Result;
            retry--;
        }

        if (!flag)
            _logger.LogError("添加域名账号失败: {Message}", latestResponse?.Message);

        return result;

        string RandName()
        {
            var sb = new StringBuilder();
            for (int i = 0; i < 6; i++)
            {
                sb.Append((char)Random.Shared.Next(97, 122));
            }

            for (int i = 0; i < 4; i++)
            {
                sb.Append(Random.Shared.Next(0, 9));
            }

            return sb.ToString();
        }
    }

    private void LogRequestError(string requestName, int emailMarketId, string response)
    {
        _logger.LogError("rushmail{RequestName}出错#{EmailMarketId}: {Response}", requestName, emailMarketId, response);
    }
}

public interface IOemEmailApplicationFactory
{
    public Task<IRushMailClient> CreateRushMailClient(int oemId);
}

public class OemEmailApplicationFactory : IOemEmailApplicationFactory
{
    private readonly IServiceProvider _sp;
    private readonly MyDbContext _dbContext;

    public OemEmailApplicationFactory(IServiceProvider sp)
    {
        _sp = sp;
        _dbContext = sp.CreateScope().ServiceProvider.GetRequiredService<MyDbContext>();
    }

    public async Task<IRushMailClient> CreateRushMailClient(int oemId)
    {
        var settings = await _dbContext.Oem.AsNoTracking().Where(o => o.Id == oemId).Select(o => o.EmailSettings)
            .FirstAsync();
        if (settings.EmailPlatform != EmailPlatform.Rushmail)
            throw new NotSupportedException($"oem[{oemId}]不支持平台rushmail");
        if (settings.UseIndependentRushmail)
        {
            return new RushMailClient(settings.RushmailCustomId, settings.RushmailSecretKey,
                logger: _sp.GetRequiredService<ILogger<RushMailClient>>());
        }

        return new RushMailClient("4376", "8679e15771654cbf5dc28918b9d609b3",
            logger: _sp.GetRequiredService<ILogger<RushMailClient>>());
    }
}

public class UniqueQueue<T> : IEnumerable<T>,
    System.Collections.ICollection,
    IReadOnlyCollection<T>
{
    private readonly Queue<T> _queue;
    private readonly HashSet<T> _hashSet;

    public UniqueQueue()
    {
        _queue = new Queue<T>();
        _hashSet = new HashSet<T>();
    }

    public void Enqueue(T item)
    {
        if (_hashSet.Add(item))
        {
            _queue.Enqueue(item);
        }
    }

    public T Dequeue()
    {
        var item = _queue.Dequeue();
        _hashSet.Remove(item);
        return item;
    }

    public bool TryDequeue([MaybeNullWhen(false)] out T result)
    {
        if (_queue.TryDequeue(out result))
        {
            _hashSet.Remove(result);
            return true;
        }

        return false;
    }

    public T Peek() => _queue.Peek();

    public IEnumerator<T> GetEnumerator() => _queue.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void CopyTo(Array array, int index)
    {
        ((ICollection)_queue).CopyTo(array, index);
    }

    int ICollection.Count => _queue.Count;
    int IReadOnlyCollection<T>.Count => _queue.Count;


    public bool IsSynchronized => true;
    public object SyncRoot => _queue;
}

public class BackgroundQueue : IHostedService
{
    private readonly IServiceProvider _sp;
    private readonly ILogger<BackgroundQueue> _logger;
    public static readonly UniqueQueue<EmailMarketing> Queue = new();

    public BackgroundQueue(IServiceProvider sp, ILogger<BackgroundQueue> logger)
    {
        _sp = sp;
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        Task.Run(() => RunAsync(cancellationToken), cancellationToken);
        return Task.CompletedTask;
    }

    private async Task RunAsync(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            if (Queue.TryDequeue(out var item))
            {
                _logger.LogInformation("开始执行营销任务#{Id}, 平台[{Platform}]", item.Id, item.EmailPlatform);
                Exception? exp = null;
                switch (item.EmailPlatform)
                {
                    case EmailPlatform.Mailjet:
                        try
                        {
                            var service1 = _sp.CreateScope().ServiceProvider
                                .GetRequiredService<MailjetApplicationService>();
                            await service1.RunAsync(item);
                        }
                        catch (Exception e)
                        {
                            exp = e;
                        }

                        break;
                    case EmailPlatform.Rushmail:
                        try
                        {
                            var service2 = _sp.CreateScope().ServiceProvider
                                .GetRequiredService<RushMailApplicationService>();
                            await service2.RunAsync(item);
                        }
                        catch (Exception e)
                        {
                            exp = e;
                        }

                        break;
                    default:
                        _logger.LogError("未知平台{Platform}", item.EmailPlatform);
                        break;
                }


                if (exp is not null)
                    _logger.LogError(exp, "执行营销任务#[{Id}]出错", item.Id);
                _logger.LogInformation("剩余营销任务数量{Count}", ((ICollection)Queue).Count);
            }

            await Task.Delay(100, cancellationToken);
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}